---
title: 贫血模型、充血模型
---
## 基于贫血模型的传统开发模式

MVC 三层架构中的 M 表示  Model，V 表示 View，C 表示 Controller。它将整个项目分为三层：展示层、逻辑层、数据层。MVC 三层开发架构是一个比较笼统的分层方式，落实到具体的开发层面，很多项目并不会 100% 遵从 MVC 固定的分层方式，而是会根据具体的项目需求，做适当的调整。


比如，现在很多 Web 或者 App 项目都是前后端分离的，后端负责暴露接口给前端调用。这种情况下，一般就将后端项目分为 Repository 层、Service 层、Controller 层 。其中，Repository 层负责数据访问，Service 层负责暴露接口。


```java title='基于贫血模型的开发模式'
////////// Controller+VO(View Object) //////////
public class UserController {
  private UserService userService; //通过构造函数或者IOC框架注入

  public UserVo getUserById(Long userId) {
    UserBo userBo = userService.getUserById(userId);
    UserVo userVo = [...convert userBo to userVo...];
    return userVo;
  }
}

public class UserVo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

////////// Service+BO(Business Object) //////////
public class UserService {
  private UserRepository userRepository; //通过构造函数或者IOC框架注入

  public UserBo getUserById(Long userId) {
    UserEntity userEntity = userRepository.getUserById(userId);
    UserBo userBo = [...convert userEntity to userBo...];
    return userBo;
  }
}

public class UserBo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

////////// Repository+Entity //////////
public class UserRepository {
  public UserEntity getUserById(Long userId) { //... }
}

public class UserEntity {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}
```
UserEntity 和 UserRepository 组成了数据访问层，UserBo 和 UserService 组成了业务逻辑层，UserVo 和 UserController在这里属于接口层。

UserBo 是一个纯粹的数据结构，只包含数据，不包含任何业务逻辑。业务逻辑集中在 UserService 中。我们通过 UserService 来操作 UserBo。换句话说，Service 层的数据和业务逻辑，被分割为 Bo 和 Service 两个类中。

像 UserBo 这样，只包含数据，不包含业务逻辑，就叫做贫血模型。同理，UserEntity、UserVo 都是基于贫血模型设计的。

这种贫血模型将数据与操作分离，破坏了面向对象的封装特性，是一种典型的面向过程的编程风格。

## 基于充血模型的 DDD 开发模式
在贫血模型中，数据和业务被分割到不同的类中。<RedSpan>充血模型（Rich Domain Model）</RedSpan>正好相反，数据和对应的业务逻辑被封装到同一个类中。因此，这种充血模型满足面向对象的封装特性，是典型的面向对象编程风格。


实际上，基于充血模型的 DDD 开发模式实现的代码，也是按照 MVC 三层架构分层的。Controller 层还是负责暴露接口，Repository 层还是负责数据存取，Service 层负责核心业务逻辑。跟基于贫血模型的传统开发模式的区别主要在 Service 层。

在基于贫血模型的传统开发模式中，Service 层包含 Service 类和 BO 类两部分，BO 是贫血模型，只包含数据，不包含具体的业务逻辑。业务逻辑集中在 Service 类中。

在基于充血模型的 DDD 开发模式中，Service 层包含 Service 类和 Domain 类两部分。Domain 就相当于贫血模型中的 BO。不过 ，Domain 与 BO 的区别在于它是基于充血模型开发的，既包含数据，也包含业务逻辑。而 Service 类变得非常单薄。


## 领域驱动设计
DDD 主要用来指导如何解耦业务系统，划分业务模块，定义业务领域模型及其交互。

在设计微服务时，核心工作就是针对公司的业务，合理地做微服务拆分。而领域模型就是用来指导划分服务的。


## 区别
上面两种开发模式，落实到代码层面，区别是一个将业务逻辑放到 Service 类中，一个将业务逻辑放到 Domain 领域模型中。

除了能看到的代码层面的区别之外（一个业务逻辑放到 Service 层，一个放到领域模型中）,还有就是开发流程的区别。

<RedSpan>基于充血模型的 DDD 开发模式的开发流程，在应对复杂业务系统的开发时更加有优势。</RedSpan>


平时的开发，大部分都是 SQL 驱动的开发模式，业务逻辑包含在一个大的 SQL 中，而 Service 能做的事情很少。SQL 都是针对特定的业务功能编写的，复用性差。
当需要开发另一个业务功能的时候，只能重新写个满足新需求的 SQL 语句。这就会导致各种长的差不多、区别很小的 SQL 语句满天飞。

所以，对于简单业务系统来说，这种开发方式问题不大。但对于复杂业务系统的开发来说，这样的开发方法会让代码越来越混乱，最终导致无法维护。

如果在项目中，使用基于充血模型的 DDD 的开发模式，需要先理清楚所有的业务，定义领域模型所包含的属性和方法。
领域模型相当于可复用的业务中间层。新功能需求的开发，都基于之前定义好的这些领域模型来完成。

越复杂的系统，对代码的复用性、易维护性要求就越高，越应该花更多的时间和精力在前期的设计上。而基于充血模型的 DDD 开发模式，正好需要前期做大量的业务调研、领域模型设计，所以更加适合这种复杂系统的开发。

## 虚拟钱包系统
应用为每个用户开设一个系统内的虚拟钱包账户，支持用户充值、提取、支付、冻结、透支、转赠、查询账户余额、查询交易流水等操作。

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117231327.png"/>

一般来讲，每个虚拟钱包账户都会对应用户的一个真实的支付账户，有可能是银行卡账户，也有可能是三方支付账户（比如支付宝、微信）。
### 功能需求
#### 1. 充值
用户通过三方支付渠道，把自己银行卡账户内的钱，充值到虚拟钱包账户中。这整个过程可以分解为三个主要的操作流程
* 从用户的银行卡账户转账到应用的公共银行卡账户；
* 将用户的充值金额加到虚拟钱余额上
* 记录刚刚这笔交易流水

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117232308.png"/>

#### 2. 支付
用户用钱包内的余额，支付购买应用内的商品。其实就是一个转账的过程，从用户的虚拟钱包转账到商家的虚拟钱包账户上。除此之外，需要记录流水。
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117232519.png"/>


#### 3. 提现

除了充值、支付之外，用户还可以将虚拟钱包中的余额，提现到自己的银行卡中。除此之外，需要记录流水。

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117232727.png"/>

#### 4. 查询余额

查看虚拟钱包中的余额数字即可。

#### 5. 查询交易流水
支持三种类型的交易流水：充值、支付、体现。在用户充值、支付、体现的时候，记录相应的交易信息。在需要查询的时候，只需要将之前记录的交易流水、按照时间、类型等条件过滤之后，显示出来即可。

### 设计思路
钱包系统的业务可以划分为两部分，其中一部分单纯跟应用内的虚拟钱包账户打交道，另一部分单纯跟银行账户打交道。
将整个钱包系统拆分为两个子系统：虚拟钱包系统和三方支付系统。
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117233116.png"/>

#### 虚拟钱包系统设计
五个功能对应虚拟钱包的操作。

| 钱包     | 虚拟钱包   |
|--------|--------|
| 充值     | +余额    |
| 体现     | -余额    |
| 支付     | +-余额   |
| 查询余额   | 查询余额   |
| 查询交易流水 | 查询交易流水 |

交易流水信息包括

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241117234000.png"/>

两个账户信息是为了兼容支付这种设计两个账户的交易类型，对于充值、提现这两种交易类型来说，只需要记录一个钱包账户信息就够了。

### 基于贫血模型的开发模式
这是一个典型的 Web 后端项目的三层结构。其中，Controller 和 VO 负责暴露接口，具体的代码实现如下所示。
```java
public class VirtualWalletController {
  // 通过构造函数或者IOC框架注入
  private VirtualWalletService virtualWalletService;

  public BigDecimal getBalance(Long walletId) { ... } //查询余额
  public void debit(Long walletId, BigDecimal amount) { ... } //出账
  public void credit(Long walletId, BigDecimal amount) { ... } //入账
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { ...} //转账
  //省略查询transaction的接口
}
```
Service 和 BO 负责核心业务逻辑，Repository 和 Entity 负责数据存取。
```java
public class VirtualWalletBo {//省略getter/setter/constructor方法
  private Long id;
  private Long createTime;
  private BigDecimal balance;
}

public Enum TransactionType {
  DEBIT,
  CREDIT,
  TRANSFER;
}

public class VirtualWalletService {
  // 通过构造函数或者IOC框架注入
  private VirtualWalletRepository walletRepo;
  private VirtualWalletTransactionRepository transactionRepo;

  public VirtualWalletBo getVirtualWallet(Long walletId) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWalletBo walletBo = convert(walletEntity);
    return walletBo;
  }

  public BigDecimal getBalance(Long walletId) {
    return walletRepo.getBalance(walletId);
  }

  @Transactional
  public void debit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    BigDecimal balance = walletEntity.getBalance();
    if (balance.compareTo(amount) < 0) {
      throw new NoSufficientBalanceException(...);
    }
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.DEBIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, balance.subtract(amount));
  }

  @Transactional
  public void credit(Long walletId, BigDecimal amount) {
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.CREDIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    BigDecimal balance = walletEntity.getBalance();
    walletRepo.updateBalance(walletId, balance.add(amount));
  }

  @Transactional
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.TRANSFER);
    transactionEntity.setFromWalletId(fromWalletId);
    transactionEntity.setToWalletId(toWalletId);
    transactionRepo.saveTransaction(transactionEntity);
    debit(fromWalletId, amount);
    credit(toWalletId, amount);
  }
}
```

### 基于充血模型的 DDD 开发模式

基于充血模型的 DDD 开发模式，跟基于贫血模型的传统开发模式的主要区别就在 Service 层，Controller 层和 Repository 层代码基本上相同。

在这种开发模式下，把虚拟钱包 VirtualWallet 类设计成一个充血 的 Domain 领域模型，并且将原来在 Service 类中的部分业务逻辑移动到 VirtualWallet 类中，让 Service 类的实现依赖 VirtualWallet 类。

具体的代码实现如下所示：

```java
public class VirtualWallet { // Domain领域模型(充血模型)
  private Long id;
  private Long createTime = System.currentTimeMillis();;
  private BigDecimal balance = BigDecimal.ZERO;

  public VirtualWallet(Long preAllocatedId) {
    this.id = preAllocatedId;
  }

  public BigDecimal balance() {
    return this.balance;
  }

  public void debit(BigDecimal amount) {
    if (this.balance.compareTo(amount) < 0) {
      throw new InsufficientBalanceException(...);
    }
    this.balance = this.balance.subtract(amount);
  }

  public void credit(BigDecimal amount) {
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException(...);
    }
    this.balance = this.balance.add(amount);
  }
}

public class VirtualWalletService {
  // 通过构造函数或者IOC框架注入
  private VirtualWalletRepository walletRepo;
  private VirtualWalletTransactionRepository transactionRepo;

  public VirtualWallet getVirtualWallet(Long walletId) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    return wallet;
  }

  public BigDecimal getBalance(Long walletId) {
    return walletRepo.getBalance(walletId);
  }

  @Transactional
  public void debit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    wallet.debit(amount);
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.DEBIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, wallet.balance());
  }

  @Transactional
  public void credit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    wallet.credit(amount);
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.CREDIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, wallet.balance());
  }

  @Transactional
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
    //...跟基于贫血模型的传统开发模式的代码一样...
  }
}

```

此时，领域模型的 VirtualWallet 类很单薄，包含的业务逻辑很简单。相对于原来的贫血模型的设计思路，这种从学模型的设计思路，没有太大优势。
这也是大部分业务系统都使用贫血模型开发的原因。

如果虚拟钱包系统需要支持更加复杂的业务逻辑，那么充血模型的优势就显现出来了。

比如要支持透支一定额度和冻结部分余额的功能。

```java
public class VirtualWallet {
  private Long id;
  private Long createTime = System.currentTimeMillis();;
  private BigDecimal balance = BigDecimal.ZERO;
  private boolean isAllowedOverdraft = true;
  private BigDecimal overdraftAmount = BigDecimal.ZERO;
  private BigDecimal frozenAmount = BigDecimal.ZERO;

  public VirtualWallet(Long preAllocatedId) {
    this.id = preAllocatedId;
  }

  public void freeze(BigDecimal amount) { ... }
  public void unfreeze(BigDecimal amount) { ...}
  public void increaseOverdraftAmount(BigDecimal amount) { ... }
  public void decreaseOverdraftAmount(BigDecimal amount) { ... }
  public void closeOverdraft() { ... }
  public void openOverdraft() { ... }

  public BigDecimal balance() {
    return this.balance;
  }

  public BigDecimal getAvaliableBalance() {
    BigDecimal totalAvaliableBalance = this.balance.subtract(this.frozenAmount);
    if (isAllowedOverdraft) {
      totalAvaliableBalance += this.overdraftAmount;
    }
    return totalAvaliableBalance;
  }

  public void debit(BigDecimal amount) {
    BigDecimal totalAvaliableBalance = getAvaliableBalance();
    if (totoalAvaliableBalance.compareTo(amount) < 0) {
      throw new InsufficientBalanceException(...);
    }
    this.balance = this.balance.subtract(amount);
  }

  public void credit(BigDecimal amount) {
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException(...);
    }
    this.balance = this.balance.add(amount);
  }
}


```
### 总结

在基于充血模型的 DDD 开发模式中，将业务逻辑移动到 Domain 中，Service 类变得单薄，但是在代码设计与实现中，并没有完全将 Service 类去掉。

区别于 Domain 的职责，Service 类主要有下面这样几个职责。

1. Service 类负责与 Repository 交互，获取数据库中的数据，转化为领域模型，然后由领域模型完成业务逻辑，最后调用 Repository 类的方法，将数据存入数据库。

2. Service 层负责跨领域模型的业务聚合功能。VirtualWalletService 类中的 transfer() 转账函数会涉及两个钱包的操作，因此这部分业务逻辑无法放到 VirtualWallet 类中。

3. Service 层负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消息、记录日志、调用其他系统 RPC 接口等，都可以放到 Service 类中。

不让领域模型和 Repository 打交道，是因为要保持领域模型的独立性，不与任何其他层的代码或开发框架耦合在一起，将流程性的代码逻辑与领域模型的业务逻辑解耦，让领域模型变得更加复用。



在基于充血模型的 DDD 开发模式中，尽管 Service 层被改造成了充血模型，但是 Controller 层和 Repository 层还是贫血模型，也不需要将其进行充血领域建模。


